﻿using System.Collections.Generic;
using System;
using System.Linq;
using System.Windows.Threading;
using System.Windows;
using System.ComponentModel;

namespace ProxyViewModel.Commands {
	/// <summary>
	/// Contains helper functions to facilitate ProxyViewModel commands
	/// </summary>
	public class ProxyCommandManager {
		private static readonly object _lock = new object();
		private static readonly IDictionary<INotifyPropertyChanged, List<WeakReference>> _invalidatorViewMoldeMap
			= new Dictionary<INotifyPropertyChanged, List<WeakReference>>();

		private static readonly PropertyChangedEventHandler _handler = (s, e) => {
			if (_invalidatorViewMoldeMap.ContainsKey((INotifyPropertyChanged)s)) {
				_invalidatorViewMoldeMap[(INotifyPropertyChanged)s].Where(v => v.IsAlive).ToList().ForEach(v => {
					object viewModel = v.Target;
					InvalidateRequerySuggested(viewModel);
				});
			}
		};

		/// <summary>
		/// Invalidates all ProxyCommands on the ViewModel
		/// </summary>
		/// <param name="viewModel">viewmodel itself</param>
		public static void InvalidateRequerySuggested(object viewModel) {
			CheckViewModelType(viewModel);
			IProxyCommandsContainer proxyCommandsContainer = (IProxyCommandsContainer)viewModel;

			if (proxyCommandsContainer.Commands != null) {
				// invalidate can be called from the constructor. If it is called from the constructor the Commands collection is not initialized yet
				proxyCommandsContainer.Commands.ToList().ForEach(e => e.Invalidate());
			}
		}

		public static void RegisterCommandsInvalidator(object invalidator, object viewModel) {
			CheckViewModelType(viewModel);
			CheckInvalidatorType(invalidator);
			lock (_lock) {
				INotifyPropertyChanged invalidatorInternal = ((INotifyPropertyChanged)invalidator);
				invalidatorInternal.PropertyChanged += _handler;
				if (!_invalidatorViewMoldeMap.ContainsKey(invalidatorInternal)) {
					_invalidatorViewMoldeMap[invalidatorInternal] = new List<WeakReference>();
				}
				if (_invalidatorViewMoldeMap[invalidatorInternal].Where(e => e.Target == viewModel).Count() == 0) {
					_invalidatorViewMoldeMap[invalidatorInternal].Add(new WeakReference(viewModel));
				}
			}
		}

		public static void UnregisterCommandsInvalidator(object invalidator, object viewModel) {
			CheckViewModelType(viewModel);
			CheckInvalidatorType(invalidator);
			lock (_lock) {
				INotifyPropertyChanged invalidatorInternal = ((INotifyPropertyChanged)invalidator);
				if (_invalidatorViewMoldeMap.ContainsKey(invalidatorInternal)) {
					WeakReference toRemove = _invalidatorViewMoldeMap[invalidatorInternal].Where(wr => wr.Target == viewModel).FirstOrDefault();
					if (toRemove != null) {
						_invalidatorViewMoldeMap[invalidatorInternal].Remove(toRemove);
					}
					if (_invalidatorViewMoldeMap[invalidatorInternal].Count == 0) {
						invalidatorInternal.PropertyChanged -= _handler;
						_invalidatorViewMoldeMap.Remove(invalidatorInternal);
					}
				}
			}
		}

		private static void CheckViewModelType(object viewModel) {
			if (!(viewModel is IProxyCommandsContainer)) {
				throw new ArgumentException("ProxyCommandManager can be used only on a ViewModel that implements IProxyCommandsContainer");
			}
		}

		private static void CheckInvalidatorType(object invalidator) {
			if (!(invalidator is INotifyPropertyChanged)) {
				throw new ArgumentException("Invalidator object must implement INotifyPropertyChanged");
			}
		}
	}
}
